package cn.zswltech.gruul.biz.service.impl;


import cn.zswltech.gruul.biz.comparator.JsonObjectComparator;
import cn.zswltech.gruul.biz.service.CustomTreeService;
import cn.zswltech.gruul.common.dal.dao.*;
import cn.zswltech.gruul.common.entity.*;
import cn.zswltech.gruul.common.util.*;
import cn.zswltech.gruul.web.api.util.AccountUtil;
import cn.zswltech.gruul.web.api.util.CookieUtils;
import cn.zswltech.gruul.web.api.util.JwtUtils;
import cn.zswltech.gruul.web.api.util.TokenUtil;
import cn.zswltech.lib.futurelog.core.LogRecord;
import cn.zswltech.lib.futurelog.core.common.OperTypeEnum;
import cn.zswltech.gruul.biz.comparator.DisplayGroupDOComparator;
import cn.zswltech.gruul.biz.service.LoginService;
import cn.zswltech.gruul.biz.service.SystemConfigService;
import cn.zswltech.gruul.biz.service.UserService;
import cn.zswltech.gruul.common.constant.UserConstants;
import cn.zswltech.gruul.common.result.MSG;
import cn.zswltech.gruul.common.result.Response;
import cn.zswltech.gruul.common.dal.query.LoginQuery;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.util.Sqls;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;


@Service("loginService")
public class LoginServiceImpl implements LoginService {
    private final static Logger logger = LoggerFactory.getLogger(LoginServiceImpl.class);

    @Autowired
    private RoleDOMapper roleDOMapper;
    @Resource
    private MenuDOMapper menuDOMapper;

    @Autowired
    private UserDOMapper userDOMapper;

    @Autowired
    private SaltDOMapper saltDOMapper;

    @Autowired
    private UserService userService;

    @Autowired
    private RoleMenuFunctionDOMapper roleMenuFunctionDOMapper;

    @Autowired
    private CustomTreeDOMapper customTreeDOMapper;

    @Resource
    UserOrgRoleDOMapper userOrgRoleDOMapper;

    @Autowired
    private FunctionDOMapper functionDOMapper;

    @Autowired
    private SystemConfigService systemConfigService;

    @Value("${system.token.expiration}")
    public long tokenExpiration;

    @Resource
    private CustomTreeService customTreeService;

    @Resource
    private OrgDOMapper orgDOMapper;



    /**
     * 登录
     *
     * @param userDO
     * @param ipAddr
     * @return
     */
    @Override
    @LogRecord(model = "用户登录", operType = OperTypeEnum.LOGIN, operator = "{#userDO.getUserName()}", success = "{#ipAddr}登录成功", fail = "{#ipAddr}登录失败")
    public Response<Map> userLogin(HttpServletResponse response, UserDO userDO, String ipAddr) {

        UserDO userDODb = new UserDO();
        BeanUtils.copyProperties(userDO, userDODb);

        Map<String, Object> rsp = new HashMap<>();
        // 生成tokenId
        String jwtToken = JwtUtils.createJWT(userDO.getId(),userDO.getPwd(), userDO.getAccount(), tokenExpiration);
        rsp.put(CookieUtils.WL_TOKEN, jwtToken);
        rsp.put("userId", userDO.getId());
        rsp.put("expiration", tokenExpiration);
        userDO.setPwd(null);
        userDO.setSalt(null);
        rsp.put("user", userDO);
        Date now = new Date();
        // 判断是否需要重置密码
        Date updatePwdTime = userDO.getUpdatePwdTime();
        if (updatePwdTime == null) {
            // 首次登录
            rsp.put("needResetPwd", true);
            rsp.put("needResetPwdReason", "首次登录，请重新设置密码");
        } else if (updatePwdTime.before(now)) {
            //密码过期
            rsp.put("needResetPwd", true);
            rsp.put("needResetPwdReason", "密码已过期，请修改密码");
        }
        try {
            userDODb.setTokenMD5(MD5Util.MD5(jwtToken));
            userDOMapper.updateByPrimaryKeySelective(userDODb);
        } catch (Exception e) {
            logger.error("save jwtToken fail", e);
        }
        try {
            String csrfToken = TokenUtil.encryptAsString(TokenUtil.getCRSFToken(jwtToken), TokenUtil.CSRF_TOKEN_PUBLICKEY);
            rsp.put("csrfToken", csrfToken);
            return Response.success(rsp);
        } catch (Exception e) {
            logger.error("create token fail", e);
            return Response.error(MSG.req_error_login_token_failed);
        }
    }


    @Override
    public Response<DisplayMenuTreeDO> getMenuTrees(UserDO loginUser) {
        DisplayMenuTreeDO displayMenuTreeDO;
        List<DisplayGroupDO> menuTree = null;

        List<Long> roleList = userOrgRoleDOMapper.selectRoleIdsByUserId(loginUser.getId());

        if (CollectionUtils.isNotEmpty(roleList)) {
            List<Map<String, Object>> menuTreeListMap = userDOMapper.selectMenuTreeByRoles(roleList);
            List<Map<String, Object>> groupListMap = userDOMapper.selectGroupByRoles(roleList);
            Map<Long, Map<Long, DisplayMenuDO>> cdmMap = new HashMap<>();
            for (Map<String, Object> menuTreeMap : menuTreeListMap) {
                Long custom_tree_id = (Long) getValFromMap(menuTreeMap, "custom_tree_id");

                Date gmtCreate = (Date) getValFromMap(menuTreeMap, "gmtCreate");
                Integer sortNo = StringUtil.obj2Integer(getValFromMap(menuTreeMap, "sortNo"));
                String menuName = (String) getValFromMap(menuTreeMap, "menuName");
                String mEnName = (String) getValFromMap(menuTreeMap, "mEnName");
                Long menu_id = (Long) getValFromMap(menuTreeMap, "menu_id");
                String menuIcon = (String) getValFromMap(menuTreeMap, "menuIcon");
                String menuPath = (String) getValFromMap(menuTreeMap, "menuPath");
                String menuTarget = (String) getValFromMap(menuTreeMap, "menuTarget");
                String menuType = (String) getValFromMap(menuTreeMap,"menuType");
                String menuCode = (String) getValFromMap(menuTreeMap, "menuCode");
                Long function_id = (Long) getValFromMap(menuTreeMap, "function_id");
                String functionName = (String) getValFromMap(menuTreeMap, "functionName");
                String fucntionCode = (String) getValFromMap(menuTreeMap, "fucntionCode");

                Map<Long, DisplayMenuDO> dmMap = cdmMap.get(custom_tree_id);
                if (dmMap == null) {
                    dmMap = new HashMap<>();
                }
                DisplayMenuDO dm = dmMap.get(menu_id);
                if (dm == null) {
                    dm = new DisplayMenuDO();
                    dm.setMenuId(menu_id);
                    dm.setMenuName(menuName);
                    dm.setCode(menuCode);
                    dm.setIcon(menuIcon);
                    dm.setPath(menuPath);
                    dm.setTarget(menuTarget);
                    dm.setType(menuType);
                    dm.setEnName(mEnName);
                    dm.setSortNo(sortNo);
                    dm.setGmtCreate(gmtCreate);
                    dmMap.put(menu_id, dm);
                }
                if (StringUtils.isNotBlank(functionName) && StringUtils.isNotBlank(fucntionCode)) {
                    List<DisplayFunciotnDO> functionList = dm.getFunctionList();
                    if (functionList == null) {
                        functionList = new ArrayList<>();
                    }
                    DisplayFunciotnDO df = new DisplayFunciotnDO();
                    df.setName(functionName);
                    df.setCode(fucntionCode);
                    df.setFuncId(function_id);
                    df.setHasPermission(true);
                    functionList.add(df);
                    dm.setFunctionList(functionList);
                }
                cdmMap.put(custom_tree_id, dmMap);
            }

            Map<Long,DisplayGroupDO> groupMap = new HashMap<>();
            for (Map<String, Object> group : groupListMap) {
                Long custom_tree_id = (Long) getValFromMap(group, "custom_tree_id");
                String customCode = (String) getValFromMap(group, "customCode");
                String customName = (String) getValFromMap(group, "customName");
                String cEnName = (String) getValFromMap(group, "cEnName");
                Date gmtCreate = (Date) getValFromMap(group, "gmtCreate");
                Integer sortNo = StringUtil.obj2Integer(getValFromMap(group, "sortNo"));
                String customIcon = (String) getValFromMap(group, "customIcon");
                Long parentId = (Long) getValFromMap(group, "parentId");
                DisplayGroupDO dgo = new DisplayGroupDO();
                dgo.setCode(customCode);
                dgo.setGroupId(custom_tree_id);
                dgo.setGroupName(customName);
                dgo.setEnName(cEnName);
                dgo.setGmtCreate(gmtCreate);
                dgo.setSortNo(sortNo);
                dgo.setIcon(customIcon);
                dgo.setParentId(parentId);
                Map<Long, DisplayMenuDO> dmMap = cdmMap.get(custom_tree_id);
                if (dmMap != null) {
                    Iterator<Long> dmit = dmMap.keySet().iterator();
                    List<JSONObject> children = new ArrayList<>();
                    while (dmit.hasNext()) {
                        Long menuId = dmit.next();
                        DisplayMenuDO dmdo = dmMap.get(menuId);
                        children.add(JSON.parseObject(JSON.toJSONString(dmdo)));
                    }
                    if (CollectionUtils.isNotEmpty(children) && children.size() > 1) {
                        children.sort(new JsonObjectComparator());
                    }
                    dgo.setChildren(children);
                }
                groupMap.put(dgo.getGroupId(),dgo);
            }
            menuTree = customTreeService.getMenu(groupMap);
        }
        if (menuTree != null) {
            menuTree.forEach(DisplayGroupDO::setOneLevelMenu);
        }
        displayMenuTreeDO = new DisplayMenuTreeDO();
        if (menuTree != null) {
            menuTree.sort(new DisplayGroupDOComparator());
            displayMenuTreeDO.setMenuTree(menuTree);
        }
        loginUser.setPwd(null);
        displayMenuTreeDO.setUser(loginUser);
        displayMenuTreeDO.setRoleList(queryRoleList(loginUser.getId()));
        displayMenuTreeDO.setOrgList(queryOrgList(loginUser.getId()));
        return Response.success(displayMenuTreeDO);
    }

    private List<OrgDO> queryOrgList(Long userId) {
        List<UserOrgRoleDO> userOrgRoleDOS = userOrgRoleDOMapper.selectByExample(Example.builder(UserOrgRoleDO.class)
                .andWhere(Sqls.custom().andEqualTo("userId", userId)).build());
        List<Long> orgIds = userOrgRoleDOS.stream().map(UserOrgRoleDO::getOrgId).distinct().collect(Collectors.toList());
        if (null == orgIds ||  orgIds.size() == 0){
            return Collections.emptyList();
        }
        return orgDOMapper.selectByExample(Example.builder(RoleDO.class).andWhere(Sqls.custom().andIn("id", orgIds)).build());
    }

    private List<RoleDO> queryRoleList(Long userId) {
        List<UserOrgRoleDO> userOrgRoleDOS = userOrgRoleDOMapper.selectByExample(Example.builder(UserOrgRoleDO.class)
                .andWhere(Sqls.custom().andEqualTo("userId", userId)).build());
        List<Long> roleIds = userOrgRoleDOS.stream().map(UserOrgRoleDO::getRoleId).distinct().collect(Collectors.toList());
        if (roleIds.size() == 0){
            return Collections.emptyList();
        }
        return roleDOMapper.selectByExample(Example.builder(RoleDO.class).andWhere(Sqls.custom().andIn("id", roleIds)).build());
    }

    @Override
    public boolean checkUserMenuOrFunction(String account, String servletPath, String method, String functionCode,Long menuId) {
        boolean rst = false;
        if (StringUtils.isBlank(account)) {
            logger.warn("account is null");
            return rst;
        }

        UserDO loginUser = userService.queryByAccount(account);
        if (loginUser == null) {
            logger.warn("user is not exist");
            return rst;
        }
        List<Long> roleList = userOrgRoleDOMapper.selectRoleIdsByUserId(loginUser.getId());
        if (CollectionUtils.isEmpty(roleList)) {
            logger.warn("user have not role permission");
            return rst;
        }
        try {
            if (StringUtils.isNotBlank(servletPath) && StringUtils.isNotBlank(method)) {
                Long functionId = null;
                Example example = new Example(FunctionDO.class);
                Example.Criteria criteria = example.createCriteria();
                criteria.andEqualTo("path",servletPath).andEqualTo("method",method);

                if(Objects.nonNull(menuId)){
                    criteria.andEqualTo("menuId",menuId);
                }
                //query.setCode(functionCode);
                List<FunctionDO> functionDOS = functionDOMapper.selectByExample(example);
                FunctionDO function = null;
                if(CollectionUtils.isNotEmpty(functionDOS)){
                    function = functionDOS.get(0);
                }
                if (function != null) {
                    functionId = function.getId();
                    //根据 path和method查询function
                    List<RoleMenuFunctionDO> roleMenuFunctionDOList = roleMenuFunctionDOMapper.queryPermisionByRoleFunction(roleList, functionId);
                    if (CollectionUtils.isEmpty(roleMenuFunctionDOList)) {
                        logger.warn("user have not function permission");
                        return rst;
                    } else {
                        rst = true;
                    }
                }
            }
        } catch (Exception e) {
            logger.error("error:", e);
            return false;
        }
        return rst;
    }

    @Override
    public boolean checkUserMenu(Long userId, String menuPath) {
        List<Long> roleIdList = userOrgRoleDOMapper.selectRoleIdsByUserId(userId);
        if (CollectionUtils.isEmpty(roleIdList)) {
            logger.warn("用户未绑定角色，无菜单权限，userId={}", userId);
            return false;
        }
        final Example menuExample = new Example(MenuDO.class);
        menuExample.createCriteria().andEqualTo("path", menuPath);
        menuExample.selectProperties("id");
        final MenuDO targetMenu = menuDOMapper.selectOneByExample(menuExample);
        if (targetMenu == null) {
            logger.warn("menuPath无对应menu，menuPath={}", menuPath);
            return false;
        }

        Example roleMenuFuncExample = new Example(RoleMenuFunctionDO.class);
        roleMenuFuncExample.createCriteria()
                .andEqualTo("menuId", targetMenu.getId())
                .andIn("roleId", roleIdList)
                .andIsNull("functionId");
        final int count = roleMenuFunctionDOMapper.selectCountByExample(roleMenuFuncExample);
        return count > 0;
    }

    @Override
    public Response checkUser(UserDO userDO, HttpServletResponse httpResponse) {
        Response response = new Response();
        UserDO queryDO = new UserDO();
        queryDO.setAccount(userDO.getAccount());

        // 校验用户密码是否正确,否返回false
        UserDO userIndb = userService.queryByAccount(userDO.getAccount());
        if (userIndb == null) {
            response.setMessage(MSG.req_error_account_not_exist);
            return response;
        }
        if (UserConstants.ACCOUNT_STATUS_LOCKED == userIndb.getStatus()) {
            response.setMessage(MSG.old_permission_user_locked);
            return response;
        }
        // 检验密码
        if (AccountUtil.checkPwd(userIndb, userDO.getPwd())) {
            response.setMessage(MSG.req_error_old_password_error);
            return response;
        }
//        // 校验user表中是否有tokenMD5,否返回false
//        if (StringUtils.isBlank(userIndb.getTokenMD5())) {
//            logger.warn("数据中tokenMd5为空，用户已经退出");
//            response.setMessage(MSG.req_error_need_login);
//            return response;
//        }
//        if (!userIndb.getTokenMD5().equalsIgnoreCase(userDO.getTokenMD5())) {
//            logger.warn("两次登录token不一样,同一个账号不同在多个地方登录");
//            response.setMessage(MSG.req_error_auth_multiple_login);
//            return response;
//        }
        return Response.success(null);
    }

    @Override
    public Response getAuthCode(LoginQuery query, HttpServletRequest request) {
        Map<String,String> salt ;
        try {
            String account = query.getAccount();
            String encryptPassword = query.getPassword();
            if (StringUtils.isBlank(account)) {
                return Response.error(MSG.req_error_param_account_null);
            }
            if (StringUtils.isBlank(encryptPassword)) {
                return Response.error(MSG.req_error_param_password_null);
            }

            //私钥解密，如果被篡改，则会解密失败
            String password = TokenUtil.decrypt(encryptPassword, TokenUtil.PWD_PRIVATEKEY);

            UserDO queryDO = new UserDO();
            // 能同时使用账号或手机号登录
            queryDO.setAccount(account);
            UserDO userIndb = userService.selectOne(queryDO);
            if (userIndb == null) {
                queryDO.setPhone(account);
                queryDO.setAccount(null);
                userIndb = userService.selectOne(queryDO);
            }
            if (userIndb == null) {
                //账户不存在，但是提示模糊一点
                return Response.error(MSG.req_error_account_or_password_failed);
            }
            if (!AccountUtil.checkPwd(userIndb, password)) {
                return Response.error(MSG.req_error_account_or_password_failed);
            }
            String origin = request.getHeader("Origin");
            String md5Sign = AccountUtil.getSalt(account, encryptPassword, origin);

            SaltDO saltDO = new SaltDO();
            saltDO.setSalt(md5Sign);
            saltDO.setExpiration(DateUtils.addMinute(new Date(), 10));
            saltDO.setGmtCreate(new Date());
            saltDO.setGmtModify(new Date());
            saltDOMapper.insertSelective(saltDO);
            salt = new HashMap<>();
            salt.put(CookieUtils.WL_SALT,md5Sign);
        } catch (Exception e) {
            logger.error("获取盐值失败", e);
            return Response.error(MSG.req_error_fetch_salt);
        }
        return Response.success(salt);
    }

    /**
     * 兼容Oracle
     *
     * @param map
     * @param key
     * @return
     */
    private Object getValFromMap(Map<String, Object> map, String key) {
        Object obj = map.get(key);
        //兼容Oracle暂时不用
//        if (obj == null) {
//            obj = map.get(key.toUpperCase());
//        }
        return obj;
    }
}
